#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
#define NEW_PREFAB_SYSTEM  // 定义新预制件系统标识，用于区分Unity版本
#endif

using UnityEngine;

namespace Spine.Unity.Examples {

#if NEW_PREFAB_SYSTEM
	[ExecuteAlways]  // 在新版Unity中，脚本会在编辑模式和播放模式下都执行
#else
	[ExecuteInEditMode]  // 在旧版Unity中，脚本在编辑模式下执行
#endif
	
	// 要求游戏对象必须附加SkeletonUtilityBone组件
	[RequireComponent(typeof(SkeletonUtilityBone))]  
	public class SkeletonUtilityGroundConstraint : SkeletonUtilityConstraint // 添加自定义约束
	{

		[Tooltip("LayerMask for what objects to raycast against")]
		public LayerMask groundMask;  // 射线检测的目标图层掩码，指定哪些层级的物体会被检测
		
		[Tooltip("Use 2D")]
		public bool use2D = false;  // 是否使用2D物理系统进行检测
		
		[Tooltip("Uses SphereCast for 3D mode and CircleCast for 2D mode")]
		public bool useRadius = false;  // 是否使用半径检测（3D用球形，2D用圆形）
		
		[Tooltip("The Radius")]
		public float castRadius = 0.1f;  // 射线检测的半径大小
		
		[Tooltip("How high above the target bone to begin casting from")]
		public float castDistance = 5f;  // 射线检测的起始高度和最大检测距离
		
		[Tooltip("X-Axis adjustment")]
		public float castOffset = 0;  // X轴偏移量，调整射线发射的水平位置
		
		[Tooltip("Y-Axis adjustment")]
		public float groundOffset = 0;  // 地面Y轴偏移量，微调检测到的地面高度
		
		[Tooltip("How fast the target IK position adjusts to the ground. Use smaller values to prevent snapping")]
		public float adjustSpeed = 5;  // 位置调整速度，较小值可防止骨骼位置突变，实现平滑过渡

		Vector3 rayOrigin;      // 射线发射起点坐标
		Vector3 rayDir = new Vector3(0, -1, 0);  // 射线方向，默认为向下
		float hitY;             // 当前帧检测到的地面碰撞点Y坐标
		float lastHitY;         // 上一帧的地面碰撞点Y坐标，用于平滑过渡

		/// <summary>
		/// 组件启用时调用，初始化状态
		/// </summary>
		protected override void OnEnable () {
			base.OnEnable();  // 调用父类的OnEnable方法
			lastHitY = transform.position.y;  // 初始化上一帧高度为当前位置的Y坐标
		}

		/// <summary>
		/// 每帧更新的主要逻辑，执行地面约束检测和骨骼位置调整
		/// </summary>
		public override void DoUpdate () {
			// 计算射线发射起点：当前位置 + X偏移 + 上方高度偏移
			rayOrigin = transform.position + new Vector3(castOffset, castDistance, 0);

			// 计算基于层级缩放的位置缩放因子
			float positionScale = hierarchy.PositionScale;
			
			// 计算当前帧最大调整距离，用于平滑过渡
			float adjustDistanceThisFrame = adjustSpeed * positionScale * Time.deltaTime;
			
			// 初始化当前帧的碰撞Y坐标为最小值
			hitY = float.MinValue;
			
			// 2D物理系统检测
			if (use2D) 
			{
				RaycastHit2D hit;  // 2D射线检测结果

				// 根据是否使用半径选择不同的检测方式
				if (useRadius)
					// 圆形检测：从起点发射圆形射线，检测圆形区域内的碰撞
					hit = Physics2D.CircleCast(rayOrigin, castRadius, rayDir, castDistance + groundOffset, groundMask);
				else
					// 线性检测：从起点发射直线射线，检测直线上的碰撞
					hit = Physics2D.Raycast(rayOrigin, rayDir, castDistance + groundOffset, groundMask);

				// 如果检测到碰撞体
				if (hit.collider != null) {
					// 计算碰撞点的Y坐标（加上地面偏移）
					hitY = hit.point.y + groundOffset;
					
					// 仅在播放模式下进行平滑过渡，编辑模式下直接使用检测结果
					if (Application.isPlaying)
						hitY = Mathf.MoveTowards(lastHitY, hitY, adjustDistanceThisFrame);
				} else {
					// 未检测到地面时，平滑过渡回骨骼的原始Y位置
					if (Application.isPlaying)
						hitY = Mathf.MoveTowards(lastHitY, transform.position.y, adjustDistanceThisFrame);
				}
			} 
			// 3D物理系统检测
			else 
			{
				RaycastHit hit;      // 3D射线检测结果
				bool validHit = false;  // 是否检测到有效碰撞

				// 根据是否使用半径选择不同的检测方式
				if (useRadius)
					// 球形检测：从起点发射球形射线，检测球体区域内的碰撞
					validHit = Physics.SphereCast(rayOrigin, castRadius, rayDir, out hit, castDistance + groundOffset, groundMask);
				else
					// 线性检测：从起点发射直线射线，检测直线上的碰撞
					validHit = Physics.Raycast(rayOrigin, rayDir, out hit, castDistance + groundOffset, groundMask);

				// 如果检测到有效碰撞
				if (validHit) {
					// 计算碰撞点的Y坐标（加上地面偏移）
					hitY = hit.point.y + groundOffset;
					// 仅在播放模式下进行平滑过渡
					if (Application.isPlaying)
						hitY = Mathf.MoveTowards(lastHitY, hitY, adjustDistanceThisFrame);

				} else {
					// 未检测到地面时，平滑过渡回骨骼的原始Y位置
					if (Application.isPlaying)
						hitY = Mathf.MoveTowards(lastHitY, transform.position.y, adjustDistanceThisFrame);
				}
			}

			// 调整骨骼位置
			Vector3 v = transform.position;
			
			// 限制Y坐标在上一帧高度和当前检测高度之间，避免穿透地面
			v.y = Mathf.Clamp(v.y, Mathf.Min(lastHitY, hitY), float.MaxValue);
			transform.position = v;

			// 更新Spine骨骼的本地坐标，需要除以层级缩放因子转换为Spine坐标系
			bone.bone.X = transform.localPosition.x / hierarchy.PositionScale;
			bone.bone.Y = transform.localPosition.y / hierarchy.PositionScale;

			// 保存当前帧的高度，供下一帧使用
			lastHitY = hitY;
		}

		/// <summary>
		/// 在Scene视图中绘制调试图形，可视化射线检测过程
		/// </summary>
		void OnDrawGizmos () 
		{
			// 计算射线碰撞终点（实际碰撞点或最大距离点）
			Vector3 hitEnd = rayOrigin + (rayDir * Mathf.Min(castDistance, rayOrigin.y - hitY));
			
			// 计算射线最大延伸终点
			Vector3 clearEnd = rayOrigin + (rayDir * castDistance);
			
			// 绘制主射线：从发射点到碰撞点
			Gizmos.DrawLine(rayOrigin, hitEnd);

			// 如果使用半径检测，绘制表示检测范围的辅助线
			if (useRadius) {
				// 在碰撞点处绘制水平线段，表示检测范围的直径
				Gizmos.DrawLine(new Vector3(hitEnd.x - castRadius, hitEnd.y - groundOffset, hitEnd.z), 
				               new Vector3(hitEnd.x + castRadius, hitEnd.y - groundOffset, hitEnd.z));
				
				// 在射线最大终点处也绘制检测范围线段
				Gizmos.DrawLine(new Vector3(clearEnd.x - castRadius, clearEnd.y, clearEnd.z), 
				               new Vector3(clearEnd.x + castRadius, clearEnd.y, clearEnd.z));
			}

			// 设置Gizmos颜色为红色，绘制射线未碰撞部分的延伸线
			Gizmos.color = Color.red;
			Gizmos.DrawLine(hitEnd, clearEnd);
		}
	}

}